package com.edushare.common.backup;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.ParsingException;
import nu.xom.ValidityException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.edushare.common.constants.ConfigPropertyName;
import com.edushare.common.global.SystemGlobals;
import com.edushare.common.util.SystemUtils;
import com.edushare.common.vo.ConfigProperties;
import com.edushare.common.vo.SystemBackup;

public class SystemBackupUtils {
	
	private static Log log = LogFactory.getLog(SystemBackupUtils.class);
	
	public static final int fileSplitSizeInMB = Integer.parseInt(SystemGlobals.getInstance().getConfigProperty(ConfigPropertyName.SYSTEM_BACKUP_FILE_SIZE_IN_MB));
	
	/**
	 * <p>
	 * Creates a new directory under STORAGE_ROOT_DIR/HOME/BACKUPS and returns path 
	 * of the directory w.r.t STORAGE_ROOT_DIR (the way files are done)
	 * </p>
	 * @return relative path (from UPLOAD_STORAGE_DIR) of newly created directory
	 */
	public static String createNewBackupDirectory(){
		
		String dirName = SystemBackup.BACKUP_DIR_PREFIX + new Date().getTime();
		
		String absPath = SystemUtils.getBackupDirectoryPath(false)
						+ "/"
						+ dirName;
		File dir = new File(absPath);
		if(!dir.exists())
			dir.mkdirs();
		
		return SystemUtils.getBackupDirectoryPath(true)+"/"+dirName;
	}
	
	
	/**
	 * Creates a MySQLDump of database, performs a bzip compression, splits into chunks, deletes the original MySQLDump
	 * @param bkp
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void backupDB(SystemBackup bkp) throws IOException, InterruptedException{
		
		String fileName = SystemBackup.DB_BKP_FILE_PREFIX;
		String DBUser = SystemGlobals.getInstance().getConfigProperty(ConfigPropertyName.SYSTEM_DATABASE_USER);
		String DBHost = SystemGlobals.getInstance().getConfigProperty(ConfigPropertyName.SYSTEM_DATABASE_HOST);
		String DBPass = SystemGlobals.getInstance().getConfigProperty(ConfigPropertyName.SYSTEM_DATABASE_PASSWORD);
		String DBPort = SystemGlobals.getInstance().getConfigProperty(ConfigPropertyName.SYSTEM_DATABASE_PORT);
		String DBName = SystemGlobals.getInstance().getConfigProperty(ConfigPropertyName.SYSTEM_DATABASE_NAME);
		
		String[] bkpCommand = {
			"/bin/sh",
			"-c",
			"mysqldump -u "+DBUser+ " -h "+DBHost+" -p"+DBPass+" "+ DBName +" > " + fileName
		};
		
		String[] tarCommand = {
				"/bin/sh",
				"-c",
				"tar -cvz "+ fileName +" | split -b "+fileSplitSizeInMB+"m -d - "+fileName+".tar.bz."+""	
		};
		
		String[] delCommand = {
				"/bin/sh",
				"-c",
				"rm -f "+ fileName	
		};
		
		File wd = getOrCreateWorkingDirectory(bkp);
		String[] envp = null;
		
		//download database back-up
		log.warn("Running MySQLDump Shell Script:\n " + bkpCommand[0] + " " + bkpCommand[1] + " " + bkpCommand[2]+"\nWorking Dir: "+wd.getAbsolutePath());
		Process p = Runtime.getRuntime().exec(bkpCommand, envp, wd);
		p.waitFor();//wait until file creation completes
		
		//compress and split using bzip 
		log.warn("Running Tar and Split Shell Script:\n " + tarCommand[0] + " " + tarCommand[1] + " " + tarCommand[2]+"\nWorking Dir: "+wd.getAbsolutePath());
		Process p1 = Runtime.getRuntime().exec(tarCommand, envp, wd);
		p1.waitFor();
		
		//remove the downloaded file
		log.warn("Running Delete Unused Script:\n " + delCommand[0] + " " + delCommand[1] + " " + delCommand[2]+"\nWorking Dir: "+wd.getAbsolutePath());
		Runtime.getRuntime().exec(delCommand, envp, wd);
		
	}
	
	/**
	 * <p>
	 * compresses MEDIA_DIRECTORY in bzip, splits into parts</p><p> 
	 * Executes following command</p>
	 * <pre>
	 * tar -C ABSOLUTE_PATH_TO_EDUSHARE_HOME_DIR -cvz MEDIA_DIR_NAME | split -b CHUNK_SIZEm -d - TAR_FILE_NAME.tar.bz.
	 * </pre>
	 * <p>It,</p><ol><li>changes directory to ABSOLUTE_PATH_TO_EDUSHARE_HOME_DIR, </li><li>archives and splits 
	 * MEDIA_DIR in series of TAR_FILE_NAME.tar.bz.* </li><li>each split file is of size CHUNK_SIZE MB</li></ol>
	 * @param bkp
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void backupMedia(SystemBackup bkp) throws IOException, InterruptedException{
		String fileName = SystemBackup.MEDIA_BKP_FILE_PREFIX;
		
		String[] zipCommand = {
				"/bin/sh",
				"-c",
				"tar -C "
					+ SystemUtils.getHomeDirectoryPath(false)
					+ " -cvz "
					+ SystemGlobals.getInstance()
						.getConfigProperty(ConfigPropertyName.STORAGE_MEDIA_DIRECTORY)
					+ " | split -b "
					+ fileSplitSizeInMB
					+ "m -d - " 
					+ fileName
					+ ".tar.bz."
		};
		
		File wd = getOrCreateWorkingDirectory(bkp);
		String[] envp = null;
		
		//zip and split media directory
		log.warn("Running Tar and Split Shell Script:\n " + zipCommand[0] + " " + zipCommand[1] + " " + zipCommand[2]+"\nWorking Dir: "+wd.getAbsolutePath());
		Process p = Runtime.getRuntime().exec(zipCommand, envp, wd);
		p.waitFor();//wait until file creation completes

	}
	
	/** calls various backup methods based on BACKUP_TYPE */
	public static void startBackup(SystemBackup bkp) throws IOException, InterruptedException {
		switch (bkp.getBackupType()) {
		case SystemBackup.BACKUP_TYPE_DB_ONLY:
			backupDB(bkp);
			break;

		case SystemBackup.BACKUP_TYPE_MEDIA_ONLY:
			backupMedia(bkp);
			break;

		case SystemBackup.BACKUP_TYPE_COMPLETE:
			backupDB(bkp);
			backupMedia(bkp);
			break;

		default:
			break;
		}
		
		BackupConfigFileProcessor.createBackupConfigFile(bkp);
	}
	
	public static ArrayList<Integer> getProcessSequence(SystemBackup bkp){
		ArrayList<Integer> processes = new ArrayList<Integer>();
		switch (bkp.getBackupType()) {
		case SystemBackup.BACKUP_TYPE_DB_ONLY:
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_DB);
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_CONFIG);			
			break;

		case SystemBackup.BACKUP_TYPE_MEDIA_ONLY:
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_MEDIA);
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_CONFIG);			
			break;

		case SystemBackup.BACKUP_TYPE_COMPLETE: default:
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_DB);
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_MEDIA);
			processes.add(ConfigProperties.SYSTEM_BACKUP_STATUS_PROCESSING_CONFIG);			
			break;
		}
		
		return processes;
	}
	
	private static File getOrCreateWorkingDirectory(SystemBackup bkp) {
		File f = new File(SystemUtils.getAbsolutePathFromRelative(bkp.getDirectoryPath()));
		if(!f.exists())
			f.mkdirs();
		return f;
	}


	public static ArrayList<String> validateConfigFile(SystemBackup backup) throws ValidityException, ParsingException, IOException {
		String dir = SystemUtils.getAbsolutePathFromRelative(backup.getDirectoryPath());
		String file = SystemBackup.CONFIG_FILE_NAME;
		File fileObj = new File(dir, file);
		Builder parser = new Builder();
		Document document = parser.build(fileObj);
		ArrayList<String> fileNames = new ArrayList<String>();
		extractAllFilesNames(document.getRootElement(),fileNames);
		return fileNames;
	}

	/** Recursively reads &lt;FILE&gt; element */
	private static void extractAllFilesNames(Element rootElement, List<String> fileNames) {
		Elements elements = rootElement.getChildElements();
		for(int i=0; i<elements.size(); i++){
			Element e = elements.get(i);
			if(		e.getQualifiedName().equals(BackupConfigFileProcessor.BACKUP_FILES)||
					e.getQualifiedName().equals(BackupConfigFileProcessor.DATABASE_BACKUP_FILES)||
					e.getQualifiedName().equals(BackupConfigFileProcessor.MEDIA_BACKUP_FILES)){
				extractAllFilesNames(e, fileNames);
			}else if(e.getQualifiedName().equals(BackupConfigFileProcessor.BACKUP_FILE)){
				fileNames.add(e.getValue());
			}
		}
	}
	
}
